<?php

/***  Documentation

    Greqo Class
    Last Update: $Date: 2009-09-27 21:58:34 +0000 (Sun, 27 Sep 2009) $
    Author: Tom at klenwell@gmail.com

DESCRIPTION
    This is the base class that all Greqo Google service should inherit from.
  
USAGE
    Intended as a base (abstract) class
    
    // authorization using GreqoBlogger extension
    $GreqoBlogger = new GreqoBlogger();
    $GreqoBlogger->email = 'yourname@gmail.com';
    $GreqoBlogger->password = '*******';
    if ( !$auth_token = $GreqoBlogger->request_clientlogin_token($service='blogger', $force=1) ) 
    {
      die('unable to get auth token');
    }

NOTES
    This class is designed to be compatible with PHP 4.  Disable strict error
    notices in PHP 5 or above.
    
    SESSION values
        $_SESSION['GDATA']['auth_token']
    
______________________________________________________________________________*/


class Greqo
{
    // public
    var $debug_ = 0;
    var $DS = DIRECTORY_SEPARATOR;
    
    // client info
    var $client_id = 'Greqo-Client-Php4';
    var $GreqoClient = null;        /* PEAR request object */
    var $GreqoResponse = null;      /* PEAR response object (in response to GreqoClient request */
    var $Mailer = null;             /* PHPMailer object */
    var $Cacher = null;             /* PEAR cache lite object */
    var $LoginData = array();       /* from conf login file */
    var $email = '';
    var $password = '';
    var $gdata_auth_key = 'Auth';
    var $gdata_auth_token = '';
    var $service = '';
    var $accountType = 'GOOGLE';
    var $allowed_redirects = 1;     // number of redirects allowed
    var $num_redirects = 0;
    var $is_redirected = 0;
    var $xml_parser_is_loaded = 0;
    
    // methods (not used -- was raising notice)
    #var $GET_  = HTTP_REQUEST_METHOD_GET;
    #var $POST_ = HTTP_REQUEST_METHOD_POST;
    
    // paths
    var $greqo_dir = '';
    var $project_dir = '';
    var $conf_dir = '';
    var $conf_file = '';
    var $lib_dir = '';
    var $etc_dir = '';
    var $tmp_dir = '';
    var $log_dir = '';
    var $pear_dir = '';
    var $pear_init = '';
    var $magpie_dir = '';
    var $phpmailer_dir = '';
    var $phpmailer_init = '';
    var $cacher_dir = '';
    var $cacher_init = '';
    var $cache_dir = '';
    var $xmlparser_init = '';
    
    // mailer settings
    var $mail_From = '';    // email address
    var $mail_FromName = 'Greqo Mailer';
    var $mail_To = '';      // email address
    var $mail_ToName = 'Greqo Admin';
    
    // cache settings
    var $auto_clean_cache_dir = 1;
    var $auto_clean_cache_ratio = 25;   
    
    // private
    var $_filename = '';
    var $_dirpath = '';


    /* MAGIC METHODS */
    // php4 constructor
    function Greqo($start_session=1)
    {
        $this->__construct($start_session);
        register_shutdown_function( array($this, '__destruct') );
    }


    // php5 constructor
    function __construct($ss)
    {
        // default
        $this->_filename = $this->_get_filename();
        $this->_dirpath = $this->_get_dirpath();
        
        // set path values      
        $this->greqo_dir = $this->_dirpath;
        $this->project_dir = dirname($this->greqo_dir) . $this->DS;
        $this->conf_dir = $this->project_dir . 'conf' . $this->DS;
        $this->conf_file = $this->conf_dir . 'core.php';
        $this->login_file = $this->conf_dir . 'login.php';
        $this->lib_dir = $this->project_dir . 'lib' . $this->DS;
        $this->etc_dir = $this->project_dir . 'etc' . $this->DS;
        $this->tmp_dir = $this->project_dir . 'tmp' . $this->DS;
        $this->log_dir = $this->tmp_dir . 'log' . $this->DS;
        $this->pear_dir = $this->lib_dir . 'pear' . $this->DS;
        $this->pear_init = $this->pear_dir . '__init__.php';
        $this->magpie_dir = $this->lib_dir . 'magpierss' . $this->DS;
        $this->magpie_init = $this->magpie_dir . 'rss_fetch.inc';
        $this->phpmailer_lib = $this->lib_dir . 'phpmailer' . $this->DS;
        $this->phpmailer_init = $this->phpmailer_lib . 'class.phpmailer.php';
        $this->cacher_dir = $this->lib_dir . 'pear_cache_lite' . $this->DS;
        $this->cacher_init = $this->cacher_dir . 'Lite.php';
        $this->cache_dir = $this->tmp_dir . 'cache' . $this->DS;
        $this->xmlparser_init = $this->lib_dir . 'xml_parser4.php';
        
        // constants
        #define('MAGPIE_CACHE_DIR', $this->cache_dir);
        #define('GREQO_CACHE_DIR', $this->cache_dir);
        
        // load core conf file
        require_once($this->conf_file);
        $this->Conf = $GreqoConfiguationSettings;
        $this->debug_ = $this->Conf['debug'];
        $this->debug('Greqo START');
        
        // dependant files
        require_once($this->pear_init);
        require_once($this->pear_dir . 'HTTP/Request.php');
        
        // start session
        if ( $ss ) $this->start_session();        
        
        // load PEAR HTTP for $GreqoClient
        if ( $this->load_http_client() )
            $this->debug('GreqoClient (HTTP_Request object) created');
        
        // preload login
        $this->_preload_login();
    }
    
    
    // destructor
    function __destruct()
    {
        $this->debug("Greqo END\n\n");
    }
    
    
    // debug
    function debug($message, $color='c33')
    {
        if ( empty($this->debug_) ) return;
        
        if ( $this->debug_ >= GREQO_DEBUG_LOG )
        {
            if ( !$this->log($message) ) trigger_error("unable to log message [$message]", E_USER_WARNING);
        }
        
        if ( $this->debug_ >= GREQO_DEBUG_PRINT )
        {
            print "<div style='line-height:1.5em; font-family:monospace; color:$color;'>$message</div>";;
        }
    }
    
    function log($message)
    {
        $mode = 'a';
        
        // Log array
        $this->Log_[] = $message;
        
        // is logging active
        if ( empty($this->log_dir) || !is_writable($this->log_dir) ) return 0;
        if ( $this->debug_ < GREQO_DEBUG_LOG ) return 0;
        
        // set file name and path
        $fname = date('Ymd') . '.log';
        $fpath = $this->log_dir . $fname;
        
        // get log file handle
        if ( !$fp = fopen($fpath, $mode) ) return 0;
        
        // write to log
        $ts = date('H:i:s');
        $logged = ( fwrite($fp, "[$ts] $message\n") === FALSE ) ? 0 : 1;
        
        fclose($fp);        
        return $logged;
    }
    
    // format print_r
    function print_r($Mixed)
    {
        $return = htmlspecialchars(print_r($Mixed, 1));
        $return = "<pre>$return</pre>";
        return $return;
    }
    
    // pretty print_r
    function pr($Mixed)
    {
        echo $this->print_r($Mixed);
    }



    /* ** PUBLIC METHODS ** */
    function get($url, $HeaderDict=array())
    {
        /* HTTP GET Request */
        $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_GET);
        $this->GreqoClient->setURL($url);
        
        foreach ( $HeaderDict as $key => $val )
            $this->GreqoClient->addHeader($key, $val);
            
        if ( !$this->_send_client_request() ) return 0;
    
        return $this->GreqoResponse->body;
    }
    
    function post($url, $PostDict=array(), $HeaderDict=array())
    {
        /* HTTP POST Request */
        $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_POST);
        $this->GreqoClient->setURL($url);
        
        foreach ( $HeaderDict as $key => $val )
            $this->GreqoClient->addHeader($key, $val);
            
        foreach ( $PostDict as $key => $val )
            $this->GreqoClient->addPostData($key, $val);
            
        if ( !$this->_send_client_request() ) return 0;
    
        return $this->GreqoResponse->body;
    }
    
    function get_url($url)
    {
        /* Simple HTTP GET Request */
        $this->GreqoClient->setURL($url);
        $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_GET);
        return $this->_send_client_request();
    }
    
    function start_session()
    {
        if ( !session_id() ) session_start(); 
    }    
    
    function request_clientlogin_token($service=null, $force=0)
    {
        /* Request token from Google server and store in session.
            $service: Google service for which token will be used (e.g. Blogger)
            $force: force request.  if false, any token in session will be returned
        */
        $gtoken = 0;
        
        // set service
        $this->service = ( !is_null($service) ) ? $service : $this->service;
        $this->debug("requesting auth token for service '{$this->service}'");
        
        // is request prepared
        if ( !$this->_is_auth_request_prepared() ) return 0;
        
        // prime session value
        $_SESSION['GDATA']['auth_token'] = ( !empty($_SESSION['GDATA']['auth_token']) ) ? $_SESSION['GDATA']['auth_token'] : 0;
        
        // can we use an existing token
        if ( !empty($_SESSION['GDATA']['auth_token']) && !$force ) 
        {
            $this->debug("returning stored token (force=0): {$_SESSION['GDATA']['auth_token']}");
            return $this->_set_auth_token($_SESSION['GDATA']['auth_token']);    
        }
        
        // prepare Greqo client
        $this->GreqoClient->setURL(GREQO_URL_CLIENTLOGIN);
        $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_POST);
        $this->GreqoClient->addHeader('Content-type', 'application/x-www-form-urlencoded');
        $this->GreqoClient->addPostData('accountType', $this->accountType);
        $this->GreqoClient->addPostData('Email', $this->email);
        $this->GreqoClient->addPostData('Passwd', $this->password);
        $this->GreqoClient->addPostData('service', $this->service);
        $this->GreqoClient->addPostData('source', $this->client_id);
        
        // send request
        if ( !$this->_send_client_request() ) return 0;
        
        // parse response
        $GDATA = $this->_parse_auth_response($this->GreqoResponse->body, 0);
        
        // success
        if ( $this->GreqoResponse->code_class == 2 )
        {
            $this->debug("*** token successfully retrieved: {$GDATA['token']}");
        }
        // failed
        elseif ( $this->GreqoResponse->code_class == 4 )
        {
            return $this->_trigger_request_error('token request failed: 400 response');
        }
  
        // save token to session
        if ( !empty($GDATA['token']) ) return $this->_set_auth_token($GDATA['token']);
        return 0;      
    }
    
    function prepare_request($request_url, $method)
    {      
        $this->GreqoClient->setURL($request_url);
        $this->GreqoClient->setMethod($method);
        
        return 1;
    }
    
    function prepare_service_request($request_url, $method, $service=null, $force=0)
    {
        // login check
        if ( !$this->_is_login_prepared() ) return 0;
        
        // set service
        if ( is_null($service) ) $service = $this->service;
        
        // get authorization token (will raise error if unsuccessful)
        if ( !$auth_token = $this->request_clientlogin_token($service, $force) ) 
           return 0;
        
        // set client url and headers
        $this->GreqoClient->addHeader('Authorization', 'GoogleLogin auth=' . $auth_token);
        $this->GreqoClient->addHeader('GData-Version', $this->gdata_version);
        $this->prepare_request($request_url, $method);
        
        return 1;
    }
     
 
    function atom_to_array($xml_string)
    {
        $AtomArray = array();    // return

        $this->load_magpie();
        if ( !$MagpieParser = new MagpieRSS($xml_string) )
        {
            $this->trigger_error(
                    sprintf('unable to parse atom [%s]: %s',
                            print_r($MagpieParser->ERROR, 1),
                            $xml_string),
                    'atom parsing error');
            return $AtomArray;
        }
        
        // build array
        foreach ( $MagpieParser->channel as $_ARR ) $AtomArray['CHANNEL'][] = $_ARR;
        foreach ( $MagpieParser->items as $_ARR ) $AtomArray['ITEM'][] = $_ARR;
        return $AtomArray;
    }
    
    function load_http_client($reset=0)
    {
        if ( !$reset && is_object($this->GreqoClient) ) return 1;
        
        $this->GreqoClient = null;
        $this->GreqoClient =& new HTTP_Request();
        return is_a($this->GreqoClient, 'HTTP_Request');
    }
    
    function reset_http_client()
    {
        $this->debug('resetting GreqoClient (HTTP_Request object)');
        return $this->load_http_client(1);
    }

    function load_magpie()
    {
        static $magpie_is_loaded = 0;
      
        if ( $magpie_is_loaded ) return;
    
        require_once($this->magpie_init);
        $magpie_is_loaded = 1;
        return;
    }
    
    function load_cacher($lifetime=3600)
    {
        // already loaded
        if ( is_object($this->Cacher) ) 
        {
            $this->Cacher->setOption('lifeTime', $lifetime);
            return;
        }
        
        // config option
        $_CONFIG = array
        (
            'cacheDir' => $this->cache_dir,
            'lifeTime' => $lifetime,
            'automaticSerialization' => FALSE,
        );
      
        // load
        require_once($this->cacher_init);
        $this->Cacher = new Cache_Lite($_CONFIG);
        
        $this->debug('Cacher loaded');
        if ( $this->debug_ ) $this->Cacher->setToDebug();
        
        // occasionally clean up cache dir
        if ( $this->auto_clean_cache_dir && time() % $this->auto_clean_cache_ratio == 1 )
        {
            $this->debug('auto-cleaning cache dir');
            $this->clean_cache();
        }
        
        return;
    }
    
    function load_xml_parser()
    {
        /* load XmlParser
          Because parser requires XML on instantiation, we don't actually
          create an object here. We just load the library.
          
          To the use:
          $Parser = new XMLParser($xml);
        */
        // load
        if ( $this->xml_parser_is_loaded ) return;
        require_once($this->xmlparser_init);
        $this->xml_parser_is_loaded = 1;
        $this->debug('xml parser library loaded');
    }
    
    function parse_xml($xml)
    {
        $this->load_xml_parser();
        if ( !$XmlParser = new XMLParser($xml) )
            return $this->trigger_error('parser failure');
        if ( !$XmlParser->parse() )
            return $this->trigger_error('parser parse failed');
        if ( !isset($XmlParser->document) )
            return $this->trigger_error('parser XML object not found');
        
        return $XmlParser->document;       
    }
    
    function explode_csv_line($csv, $sp=',')
    {
        /* http://www.php.net/manual/en/function.split.php#72564 */
        $ElementList = array();
        
        $csv = str_replace('""', "'", $csv);
        $bits = explode('"',$csv);
        
        for ( $i=0; $i < count($bits) ; $i++ )
        {
            /*
                odd numbered elements would have been enclosed by double quotes
                even numbered elements would not have been
            */
            if ( ($i%2) == 1 )
            {
                $ElementList[] = $bits[$i];
            }
            else
            {
                $rest = $bits[$i];
                $rest = preg_replace("/^".$sp."/","",$rest);
                $rest = preg_replace("/".$sp."$/","",$rest);
                $ElementList = array_merge($ElementList,explode($sp,$rest));
            }
        }
        return $ElementList;
    }    
    
    function clean_cache()
    {
        /* cleans cache
            see http://preview.tinyurl.com/coo2bj for PEAR docs
        */

        $this->debug('attempting to clean cache');
        
        if ( !$this->Cacher )
        {
            $this->debug('no cache object found');
            return 0;
        }
        
        if ( $this->Cacher->clean(false, 'old') ) return 1;
    }
    
    function load_mailer()
    {
        // already loaded
        if ( is_object($this->Mailer) ) return;

        // load
        require_once($this->greqo_dir . 'gmail.class.php');
        $this->Mailer = new GmailMailer();
        
        // set Mailer properties to login settings
        $this->Mailer->Username = $this->email;
        $this->Mailer->Password = $this->password;
        $this->Mailer->Sender = $this->email;
        $this->Mailer->From = $this->email;
        
        return;
    }
    
    function send_mail($subject, $body, $ToList=array())
    {
        /*
            Sends an email using Greqo PhpMailer Gmail class.
            
            ToList can be an email or an array of email addresses or an
            array of arrays holding email address and name.             
        */
        if ( !is_object($this->Mailer) ) $this->load_mailer();
        
        $this->Mailer->Subject = $subject;
        $this->Mailer->Body = $body;
        
        // set to list
        if ( !is_array($ToList) ) $ToList = array( $ToList );
        foreach ( $ToList as $Tuple )
        {
            if ( !is_array($Tuple) ) $Tuple = array($Tuple);
            if ( !isset($Tuple[1]) ) $Tuple[1] = $Tuple[0];
            $this->Mailer->AddAddress($Tuple[0], $Tuple[1]);
        }
        
        return $this->Mailer->send_email();
    }
    
    function escape_entities($string)
    {
        $escaped_string = '';    // return
        
        // escape
        $escaped_string = htmlspecialchars($string, ENT_QUOTES);
        $escaped_string = preg_replace('/&#0*39;/', '&apos;', $escaped_string);
        
        return $escaped_string;
    }
    
    function get_datestamp($uts=0)
    {
        /* convert to RFC 3339 datestamp ([YYYY]-[MM]-[DD]T[hh]:[mm]) */
        if ( !$uts ) $uts = time();
        return date(GREQO_DATESTAMP_FORMAT, $uts);
    }
    
    function rfc3339($date)
    {
        $uts = ( (int)strtotime($date) > 0 ) ? strtotime($date) : $date;
        
        // php4 can't parse rfc3339
        if ( strlen($uts) >= 20 && $uts[10] == 'T' && $uts[19] == 'Z' )
            $uts = strtotime(substr(str_replace('T', ' ', $uts), 0, 19));
        
        if ( date('Y-m-d', $uts) == '1969-12-31' )
        {
            trigger_error("invalid date string: $date\nwill set to current time",
                E_USER_WARNING);
            $uts = time();
        }
        return $this->get_datestamp($uts);
    }
    
    function absolute_url($domain, $url)
    {
        /* add the domain to front of url if not there */
        if ( strpos($url, $domain) !== false ) return $url;
        
        $domain = ( $domain{strlen($domain)-1} == '/' ) ? $domain :  $domain.'/';
        $url = ( $url{0} == '/' ) ? substr($url, 1) : $url; 
        return $domain . $url;
    }
    
    function trigger_error($message='none', $type='greqo base') 
    {
        $em = sprintf('%s error: %s', $type, $message);
        $this->GreqoClient->_postData['Passwd'] = $this->password = '**scrubbed**';
        trigger_error($em, E_USER_WARNING);
        
        $logf = <<<XLOG
%s
-- Request Object --
%s

-- GreqoClient Object --
%s
XLOG;
        $this->debug(sprintf($logf, $em,
                             print_r($this->GreqoResponse, 1),
                             print_r($this->GreqoClient, 1)
                            )
                    );
        
        return 0;
    }



    /* ** PRIVATE METHODS ** */
    function _preload_login()
    {
        if ( !file_exists($this->login_file) )
        {
            $this->debug("preload login failed: {$this->login_file} not found");
            return 0;
        }
        include($this->login_file);
        
        if ( isset($GoogleAccount['default']) )
            $this->LoginData = $GoogleAccount['default'];
        
        if ( isset($GoogleAccount['default']['email']) )
        {
            $this->email = $GoogleAccount['default']['email'];
            $this->debug("preload login: email set to {$this->email}");
        }
        if ( isset($GoogleAccount['default']['password']) )
        {
            $this->password = $GoogleAccount['default']['password'];
            $this->debug("preload login: password set");
        }

        return 1;
    }

    
    function _send_client_request($is_redirect=0)
    {
        // is this a redirect
        if ( $is_redirect )
        {
            $this->num_redirects++;
            $this->GreqoClient->setURL($this->redirect_url);
        }
        else
        {
            $this->num_redirects = 0;
        }        
    
        // send request
        $this->debug("sending client request to url: {$this->GreqoClient->_url->url}");
        $this->GreqoResponse = $this->GreqoClient->sendRequest();
      
        // error check
        if ( PEAR::isError($this->GreqoResponse) ) return $this->_trigger_request_error('PEAR error on request'); 
      
        // process response
        if ( !is_object($this->GreqoResponse) ) $this->GreqoResponse = new stdClass();
        $this->GreqoResponse->code = $this->GreqoClient->getResponseCode();
        $this->GreqoResponse->code_class = floor($this->GreqoResponse->code / 100);
        $this->GreqoResponse->body = $this->GreqoClient->getResponseBody();
        $this->GreqoResponse->HEADERS = $this->GreqoClient->getResponseHeader();
        
        // reorganize cookies
        $this->GreqoResponse->CookieDict = array();
        $this->GreqoResponse->CookieList = $this->GreqoClient->getResponseCookies();
        if ( is_array($this->GreqoResponse->CookieList) )
        {
            foreach ( range(0, count($this->GreqoResponse->CookieList)-1) as $i )
            {
                $CookieData = $this->GreqoResponse->CookieList[$i];
                $this->GreqoResponse->CookieDict[$CookieData['name']] = array(
                    'num'       => $i,
                    'name'      => $CookieData['name'],
                    'value'     => $CookieData['value'],
                    'expires'   => $CookieData['expires'],
                    'domain'    => $CookieData['domain'],
                    'path'      => $CookieData['path'],
                    'secure'    => $CookieData['secure']
                );
            }
        }
        
        $this->debug(sprintf("request response:\n%s",print_r($this->GreqoResponse,1)));
      
        // redirect code (redirects allowed)
        if ( $this->GreqoResponse->code_class == 3 &&
                $this->num_redirects <= $this->allowed_redirects )
        {           
            if ( $this->redirect_url = $this->GreqoClient->getResponseHeader('Location') )
            {
                $this->debug("redirect code {$this->GreqoResponse->code}: redirect to {$this->redirect_url}");
                return $this->_send_client_request(1);
            }
            else
            {
                return $this->_trigger_request_error("Redirect Code {$this->GreqoResponse->code}: Location header not found");
            }
        }  
        // redirect code: (redirects not allowed)
        elseif ( $this->GreqoResponse->code_class == 3 )
        {
            return $this->_trigger_request_error("Redirect Code {$this->GreqoResponse->code}: redirect limit exceeded ({$this->num_redirects} redirects)");
        }
      
        return 1;
    }
    
    function _parse_auth_response($body='', $show_google_error=1)
    {
        $GDATA = array();
        
        // sanity check
        if ( empty($body) ) return $this->_trigger_request_error('empty response from Google');

        // parse response
        $_LINE = explode("\n", $body);
        foreach ( $_LINE as $_string )
        {
            $_string = trim($_string);
            if ( !strpos($_string, '=') ) continue;
        
            $_PARAM = explode('=', $_string);
            $GDATA[$_PARAM[0]] = $_PARAM[1];
        }
      
        if ( empty($GDATA) )
        {
            return $this->_trigger_request_error('no GData values found');
        }
        elseif ( !empty($GDATA['Error']) && $show_google_error )
        {
            $this->_trigger_request_error("Google Error Response: {$GDATA['Error']}");
        }
      
        // Set Auth as Token
        if ( $_PARAM[0] == $this->gdata_auth_key ) $GDATA['token'] = $_PARAM[1];
        return $GDATA;
    }
    
    function _set_auth_token($token)
    {
        $this->gdata_auth_token = $token;
        $_SESSION['GDATA']['auth_token'] = $token;
        $this->debug("Google auth token set to: {$_SESSION['GDATA']['auth_token']}");
        return $token;
    }
 
    function _trigger_request_error($message='none')
    {
        return $this->trigger_error($message, 'request');
    }
    
    function _is_auth_request_prepared()
    {
        if ( !$this->_is_login_prepared() ) return 0;
      
        if ( empty($this->service) )
        {
            $this->_trigger_request_error('service must be set');
            return 0;
        }
      
        return 1;      
    }
    
    function _is_login_prepared()
    {    
        if ( empty($this->email) )
            return $this->_trigger_request_error('email/login must be set');
      
        if ( empty($this->password) )
            return $this->_trigger_request_error('password must be set');
      
        return 1;
    }

    function _get_filename() { return basename(__FILE__); }
    function _get_dirpath() { return dirname(__FILE__) . $this->DS; }

} // end class

?>
